# Copyright (c) 2006-2008 The Regents of The University of Michigan
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import sys
from os.path import (
    basename,
    exists,
    isdir,
    isfile,
    islink,
)
from os.path import join as joinpath
from os.path import normpath

spec_dist = os.environ.get("M5_CPU2000", "/dist/m5/cpu2000")


def copyfiles(srcdir, dstdir):
    from filecmp import cmp as filecmp
    from shutil import copyfile

    srcdir = normpath(srcdir)
    dstdir = normpath(dstdir)

    if not isdir(dstdir):
        os.mkdir(dstdir)

    for root, dirs, files in os.walk(srcdir):
        root = normpath(root)
        prefix = os.path.commonprefix([root, srcdir])

        root = root[len(prefix) :]
        if root.startswith("/"):
            root = root[1:]

        for entry in dirs:
            newdir = joinpath(dstdir, root, entry)
            if not isdir(newdir):
                os.mkdir(newdir)

        for entry in files:
            dest = normpath(joinpath(dstdir, root, entry))
            src = normpath(joinpath(srcdir, root, entry))
            if not isfile(dest) or not filecmp(src, dest):
                copyfile(src, dest)

    # some of the spec benchmarks expect to be run from one directory up.
    # just create some symlinks that solve the problem
    inlink = joinpath(dstdir, "input")
    outlink = joinpath(dstdir, "output")
    if not exists(inlink):
        os.symlink(".", inlink)
    if not exists(outlink):
        os.symlink(".", outlink)


class Benchmark:
    def __init__(self, isa, os, input_set):
        if not hasattr(self.__class__, "name"):
            self.name = self.__class__.__name__

        if not hasattr(self.__class__, "binary"):
            self.binary = self.name

        if not hasattr(self.__class__, "args"):
            self.args = []

        if not hasattr(self.__class__, "output"):
            self.output = f"{self.name}.out"

        if not hasattr(self.__class__, "simpoint"):
            self.simpoint = None

        try:
            func = getattr(self.__class__, input_set)
        except AttributeError:
            raise AttributeError(
                f"The benchmark {self.name} does not have the {input_set} input set"
            )

        executable = joinpath(spec_dist, "binaries", isa, os, self.binary)
        if not isfile(executable):
            raise AttributeError(f"{executable} not found")
        self.executable = executable

        # root of tree for input & output data files
        data_dir = joinpath(spec_dist, "data", self.name)
        # optional subtree with files shared across input sets
        all_dir = joinpath(data_dir, "all")
        # dirs for input & output files for this input set
        inputs_dir = joinpath(data_dir, input_set, "input")
        outputs_dir = joinpath(data_dir, input_set, "output")
        # keep around which input set was specified
        self.input_set = input_set

        if not isdir(inputs_dir):
            raise AttributeError(f"{inputs_dir} not found")

        self.inputs_dir = [inputs_dir]
        if isdir(all_dir):
            self.inputs_dir += [joinpath(all_dir, "input")]
        if isdir(outputs_dir):
            self.outputs_dir = outputs_dir

        if not hasattr(self.__class__, "stdin"):
            self.stdin = joinpath(inputs_dir, f"{self.name}.in")
            if not isfile(self.stdin):
                self.stdin = None

        if not hasattr(self.__class__, "stdout"):
            self.stdout = joinpath(outputs_dir, f"{self.name}.out")
            if not isfile(self.stdout):
                self.stdout = None

        func(self, isa, os)

    def makeProcessArgs(self, **kwargs):
        # set up default args for Process object
        process_args = {}
        process_args["cmd"] = [self.name] + self.args
        process_args["executable"] = self.executable
        if self.stdin:
            process_args["input"] = self.stdin
        if self.stdout:
            process_args["output"] = self.stdout
        if self.simpoint:
            process_args["simpoint"] = self.simpoint
        # explicit keywords override defaults
        process_args.update(kwargs)

        return process_args

    def makeProcess(self, **kwargs):
        process_args = self.makeProcessArgs(**kwargs)

        # figure out working directory: use m5's outdir unless
        # overridden by Process's cwd param
        cwd = process_args.get("cwd")

        if not cwd:
            from m5 import options

            cwd = options.outdir
            process_args["cwd"] = cwd
        if not isdir(cwd):
            os.makedirs(cwd)
        # copy input files to working directory
        for d in self.inputs_dir:
            copyfiles(d, cwd)
        # generate Process object
        from m5.objects import Process

        return Process(**process_args)

    def __str__(self):
        return self.name


class DefaultBenchmark(Benchmark):
    def ref(self, isa, os):
        pass

    def test(self, isa, os):
        pass

    def train(self, isa, os):
        pass


class MinneDefaultBenchmark(DefaultBenchmark):
    def smred(self, isa, os):
        pass

    def mdred(self, isa, os):
        pass

    def lgred(self, isa, os):
        pass


class ammp(MinneDefaultBenchmark):
    name = "ammp"
    number = 188
    lang = "C"
    simpoint = 108 * 100e6


class applu(MinneDefaultBenchmark):
    name = "applu"
    number = 173
    lang = "F77"
    simpoint = 2179 * 100e6


class apsi(MinneDefaultBenchmark):
    name = "apsi"
    number = 301
    lang = "F77"
    simpoint = 3408 * 100e6


class art(DefaultBenchmark):
    name = "art"
    number = 179
    lang = "C"

    def test(self, isa, os):
        self.args = [
            "-scanfile",
            "c756hel.in",
            "-trainfile1",
            "a10.img",
            "-stride",
            "2",
            "-startx",
            "134",
            "-starty",
            "220",
            "-endx",
            "139",
            "-endy",
            "225",
            "-objects",
            "1",
        ]
        self.output = "test.out"

    def train(self, isa, os):
        self.args = [
            "-scanfile",
            "c756hel.in",
            "-trainfile1",
            "a10.img",
            "-stride",
            "2",
            "-startx",
            "134",
            "-starty",
            "220",
            "-endx",
            "184",
            "-endy",
            "240",
            "-objects",
            "3",
        ]
        self.output = "train.out"

    def lgred(self, isa, os):
        self.args = [
            "-scanfile",
            "c756hel.in",
            "-trainfile1",
            "a10.img",
            "-stride",
            "5",
            "-startx",
            "134",
            "-starty",
            "220",
            "-endx",
            "184",
            "-endy",
            "240",
            "-objects",
            "1",
        ]
        self.output = "lgred.out"


class art110(art):
    def ref(self, isa, os):
        self.args = [
            "-scanfile",
            "c756hel.in",
            "-trainfile1",
            "a10.img",
            "-trainfile2",
            "hc.img",
            "-stride",
            "2",
            "-startx",
            "110",
            "-starty",
            "200",
            "-endx",
            "160",
            "-endy",
            "240",
            "-objects",
            "10",
        ]
        self.output = "ref.1.out"
        self.simpoint = 340 * 100e6


class art470(art):
    def ref(self, isa, os):
        self.args = [
            "-scanfile",
            "c756hel.in",
            "-trainfile1",
            "a10.img",
            "-trainfile2",
            "hc.img",
            "-stride",
            "2",
            "-startx",
            "470",
            "-starty",
            "140",
            "-endx",
            "520",
            "-endy",
            "180",
            "-objects",
            "10",
        ]
        self.output = "ref.2.out"
        self.simpoint = 365 * 100e6


class equake(DefaultBenchmark):
    name = "equake"
    number = 183
    lang = "C"
    simpoint = 812 * 100e6

    def lgred(self, isa, os):
        pass


class facerec(MinneDefaultBenchmark):
    name = "facerec"
    number = 187
    lang = "F"
    simpoint = 375 * 100e6


class fma3d(MinneDefaultBenchmark):
    name = "fma3d"
    number = 191
    lang = "F"
    simpoint = 2541 * 100e6


class galgel(MinneDefaultBenchmark):
    name = "galgel"
    number = 178
    lang = "F"
    simpoint = 2491 * 100e6


class lucas(MinneDefaultBenchmark):
    name = "lucas"
    number = 189
    lang = "F"
    simpoint = 545 * 100e6


class mesa(Benchmark):
    name = "mesa"
    number = 177
    lang = "C"
    stdin = None

    def __set_args(self, frames):
        self.args = [
            "-frames",
            frames,
            "-meshfile",
            f"{self.name}.in",
            "-ppmfile",
            f"{self.name}.ppm",
        ]

    def test(self, isa, os):
        self.__set_args("10")

    def train(self, isa, os):
        self.__set_args("500")

    def ref(self, isa, os):
        self.__set_args("1000")
        self.simpoint = 1135 * 100e6

    def lgred(self, isa, os):
        self.__set_args("1")


class mgrid(MinneDefaultBenchmark):
    name = "mgrid"
    number = 172
    lang = "F77"
    simpoint = 3292 * 100e6


class sixtrack(DefaultBenchmark):
    name = "sixtrack"
    number = 200
    lang = "F77"
    simpoint = 3043 * 100e6

    def lgred(self, isa, os):
        pass


class swim(MinneDefaultBenchmark):
    name = "swim"
    number = 171
    lang = "F77"
    simpoint = 2079 * 100e6


class wupwise(DefaultBenchmark):
    name = "wupwise"
    number = 168
    lang = "F77"
    simpoint = 3237 * 100e6

    def lgred(self, isa, os):
        pass


class bzip2(DefaultBenchmark):
    name = "bzip2"
    number = 256
    lang = "C"

    def test(self, isa, os):
        self.args = ["input.random"]

    def train(self, isa, os):
        self.args = ["input.compressed"]


class bzip2_source(bzip2):
    def ref(self, isa, os):
        self.simpoint = 977 * 100e6
        self.args = ["input.source", "58"]

    def lgred(self, isa, os):
        self.args = ["input.source", "1"]


class bzip2_graphic(bzip2):
    def ref(self, isa, os):
        self.simpoint = 718 * 100e6
        self.args = ["input.graphic", "58"]

    def lgred(self, isa, os):
        self.args = ["input.graphic", "1"]


class bzip2_program(bzip2):
    def ref(self, isa, os):
        self.simpoint = 458 * 100e6
        self.args = ["input.program", "58"]

    def lgred(self, isa, os):
        self.args = ["input.program", "1"]


class crafty(MinneDefaultBenchmark):
    name = "crafty"
    number = 186
    lang = "C"
    simpoint = 774 * 100e6


class eon(MinneDefaultBenchmark):
    name = "eon"
    number = 252
    lang = "CXX"
    stdin = None


class eon_kajiya(eon):
    args = [
        "chair.control.kajiya",
        "chair.camera",
        "chair.surfaces",
        "chair.kajiya.ppm",
        "ppm",
        "pixels_out.kajiya",
    ]
    output = "kajiya_log.out"


class eon_cook(eon):
    args = [
        "chair.control.cook",
        "chair.camera",
        "chair.surfaces",
        "chair.cook.ppm",
        "ppm",
        "pixels_out.cook",
    ]
    output = "cook_log.out"


class eon_rushmeier(eon):
    args = [
        "chair.control.rushmeier",
        "chair.camera",
        "chair.surfaces",
        "chair.rushmeier.ppm",
        "ppm",
        "pixels_out.rushmeier",
    ]
    output = "rushmeier_log.out"
    simpoint = 403 * 100e6


class gap(DefaultBenchmark):
    name = "gap"
    number = 254
    lang = "C"

    def __set_args(self, size):
        self.args = ["-l", "./", "-q", "-m", size]

    def test(self, isa, os):
        self.__set_args("64M")

    def train(self, isa, os):
        self.__set_args("128M")

    def ref(self, isa, os):
        self.__set_args("192M")
        self.simpoint = 674 * 100e6

    def lgred(self, isa, os):
        self.__set_args("64M")

    def mdred(self, isa, os):
        self.__set_args("64M")

    def smred(self, isa, os):
        self.__set_args("64M")


class gcc(DefaultBenchmark):
    name = "gcc"
    number = 176
    lang = "C"

    def test(self, isa, os):
        self.args = ["cccp.i", "-o", "cccp.s"]

    def train(self, isa, os):
        self.args = ["cp-decl.i", "-o", "cp-decl.s"]

    def smred(self, isa, os):
        self.args = ["c-iterate.i", "-o", "c-iterate.s"]

    def mdred(self, isa, os):
        self.args = ["rdlanal.i", "-o", "rdlanal.s"]

    def lgred(self, isa, os):
        self.args = ["cp-decl.i", "-o", "cp-decl.s"]


class gcc_166(gcc):
    def ref(self, isa, os):
        self.simpoint = 389 * 100e6
        self.args = ["166.i", "-o", "166.s"]


class gcc_200(gcc):
    def ref(self, isa, os):
        self.simpoint = 736 * 100e6
        self.args = ["200.i", "-o", "200.s"]


class gcc_expr(gcc):
    def ref(self, isa, os):
        self.simpoint = 36 * 100e6
        self.args = ["expr.i", "-o", "expr.s"]


class gcc_integrate(gcc):
    def ref(self, isa, os):
        self.simpoint = 4 * 100e6
        self.args = ["integrate.i", "-o", "integrate.s"]


class gcc_scilab(gcc):
    def ref(self, isa, os):
        self.simpoint = 207 * 100e6
        self.args = ["scilab.i", "-o", "scilab.s"]


class gzip(DefaultBenchmark):
    name = "gzip"
    number = 164
    lang = "C"

    def test(self, isa, os):
        self.args = ["input.compressed", "2"]

    def train(self, isa, os):
        self.args = ["input.combined", "32"]


class gzip_source(gzip):
    def ref(self, isa, os):
        self.simpoint = 334 * 100e6
        self.args = ["input.source", "1"]

    def smred(self, isa, os):
        self.args = ["input.source", "1"]

    def mdred(self, isa, os):
        self.args = ["input.source", "1"]

    def lgred(self, isa, os):
        self.args = ["input.source", "1"]


class gzip_log(gzip):
    def ref(self, isa, os):
        self.simpoint = 265 * 100e6
        self.args = ["input.log", "60"]

    def smred(self, isa, os):
        self.args = ["input.log", "1"]

    def mdred(self, isa, os):
        self.args = ["input.log", "1"]

    def lgred(self, isa, os):
        self.args = ["input.log", "1"]


class gzip_graphic(gzip):
    def ref(self, isa, os):
        self.simpoint = 653 * 100e6
        self.args = ["input.graphic", "60"]

    def smred(self, isa, os):
        self.args = ["input.graphic", "1"]

    def mdred(self, isa, os):
        self.args = ["input.graphic", "1"]

    def lgred(self, isa, os):
        self.args = ["input.graphic", "1"]


class gzip_random(gzip):
    def ref(self, isa, os):
        self.simpoint = 623 * 100e6
        self.args = ["input.random", "60"]

    def smred(self, isa, os):
        self.args = ["input.random", "1"]

    def mdred(self, isa, os):
        self.args = ["input.random", "1"]

    def lgred(self, isa, os):
        self.args = ["input.random", "1"]


class gzip_program(gzip):
    def ref(self, isa, os):
        self.simpoint = 1189 * 100e6
        self.args = ["input.program", "60"]

    def smred(self, isa, os):
        self.args = ["input.program", "1"]

    def mdred(self, isa, os):
        self.args = ["input.program", "1"]

    def lgred(self, isa, os):
        self.args = ["input.program", "1"]


class mcf(MinneDefaultBenchmark):
    name = "mcf"
    number = 181
    lang = "C"
    args = ["mcf.in"]
    simpoint = 553 * 100e6


class parser(MinneDefaultBenchmark):
    name = "parser"
    number = 197
    lang = "C"
    args = ["2.1.dict", "-batch"]
    simpoint = 1146 * 100e6


class perlbmk(DefaultBenchmark):
    name = "perlbmk"
    number = 253
    lang = "C"

    def test(self, isa, os):
        self.args = ["-I.", "-I", "lib", "test.pl"]
        self.stdin = "test.in"


class perlbmk_diffmail(perlbmk):
    def ref(self, isa, os):
        self.simpoint = 141 * 100e6
        self.args = [
            "-I",
            "lib",
            "diffmail.pl",
            "2",
            "550",
            "15",
            "24",
            "23",
            "100",
        ]

    def train(self, isa, os):
        self.args = [
            "-I",
            "lib",
            "diffmail.pl",
            "2",
            "350",
            "15",
            "24",
            "23",
            "150",
        ]


class perlbmk_scrabbl(perlbmk):
    def train(self, isa, os):
        self.args = ["-I.", "-I", "lib", "scrabbl.pl"]
        self.stdin = "scrabbl.in"


class perlbmk_makerand(perlbmk):
    def ref(self, isa, os):
        self.simpoint = 11 * 100e6
        self.args = ["-I", "lib", "makerand.pl"]

    def lgred(self, isa, os):
        self.args = ["-I.", "-I", "lib", "lgred.makerand.pl"]

    def mdred(self, isa, os):
        self.args = ["-I.", "-I", "lib", "mdred.makerand.pl"]

    def smred(self, isa, os):
        self.args = ["-I.", "-I", "lib", "smred.makerand.pl"]


class perlbmk_perfect(perlbmk):
    def ref(self, isa, os):
        self.simpoint = 5 * 100e6
        self.args = ["-I", "lib", "perfect.pl", "b", "3", "m", "4"]

    def train(self, isa, os):
        self.args = ["-I", "lib", "perfect.pl", "b", "3"]


class perlbmk_splitmail1(perlbmk):
    def ref(self, isa, os):
        self.simpoint = 405 * 100e6
        self.args = [
            "-I",
            "lib",
            "splitmail.pl",
            "850",
            "5",
            "19",
            "18",
            "1500",
        ]


class perlbmk_splitmail2(perlbmk):
    def ref(self, isa, os):
        self.args = [
            "-I",
            "lib",
            "splitmail.pl",
            "704",
            "12",
            "26",
            "16",
            "836",
        ]


class perlbmk_splitmail3(perlbmk):
    def ref(self, isa, os):
        self.args = [
            "-I",
            "lib",
            "splitmail.pl",
            "535",
            "13",
            "25",
            "24",
            "1091",
        ]


class perlbmk_splitmail4(perlbmk):
    def ref(self, isa, os):
        self.args = [
            "-I",
            "lib",
            "splitmail.pl",
            "957",
            "12",
            "23",
            "26",
            "1014",
        ]


class twolf(Benchmark):
    name = "twolf"
    number = 300
    lang = "C"
    stdin = None

    def test(self, isa, os):
        self.args = ["test"]

    def train(self, isa, os):
        self.args = ["train"]

    def ref(self, isa, os):
        self.simpoint = 1066 * 100e6
        self.args = ["ref"]

    def smred(self, isa, os):
        self.args = ["smred"]

    def mdred(self, isa, os):
        self.args = ["mdred"]

    def lgred(self, isa, os):
        self.args = ["lgred"]


class vortex(Benchmark):
    name = "vortex"
    number = 255
    lang = "C"
    stdin = None

    def __init__(self, isa, os, input_set):
        if isa in ("arm", "thumb", "aarch64"):
            self.endian = "lendian"
        elif isa == "sparc" or isa == "sparc32":
            self.endian = "bendian"
        else:
            raise AttributeError(f"unknown ISA {isa}")

        super().__init__(isa, os, input_set)

    def test(self, isa, os):
        self.args = [f"{self.endian}.raw"]
        self.output = "vortex.out"

    def train(self, isa, os):
        self.args = [f"{self.endian}.raw"]
        self.output = "vortex.out"

    def smred(self, isa, os):
        self.args = [f"{self.endian}.raw"]
        self.output = "vortex.out"

    def mdred(self, isa, os):
        self.args = [f"{self.endian}.raw"]
        self.output = "vortex.out"

    def lgred(self, isa, os):
        self.args = [f"{self.endian}.raw"]
        self.output = "vortex.out"


class vortex1(vortex):
    def ref(self, isa, os):
        self.args = [f"{self.endian}1.raw"]
        self.output = "vortex1.out"
        self.simpoint = 271 * 100e6


class vortex2(vortex):
    def ref(self, isa, os):
        self.simpoint = 1024 * 100e6
        self.args = [f"{self.endian}2.raw"]
        self.output = "vortex2.out"


class vortex3(vortex):
    def ref(self, isa, os):
        self.simpoint = 564 * 100e6
        self.args = [f"{self.endian}3.raw"]
        self.output = "vortex3.out"


class vpr(MinneDefaultBenchmark):
    name = "vpr"
    number = 175
    lang = "C"


# not sure about vpr minnespec place.in
class vpr_place(vpr):
    args = [
        "net.in",
        "arch.in",
        "place.out",
        "dum.out",
        "-nodisp",
        "-place_only",
        "-init_t",
        "5",
        "-exit_t",
        "0.005",
        "-alpha_t",
        "0.9412",
        "-inner_num",
        "2",
    ]
    output = "place_log.out"


class vpr_route(vpr):
    simpoint = 476 * 100e6
    args = [
        "net.in",
        "arch.in",
        "place.in",
        "route.out",
        "-nodisp",
        "-route_only",
        "-route_chan_width",
        "15",
        "-pres_fac_mult",
        "2",
        "-acc_fac",
        "1",
        "-first_iter_pres_fac",
        "4",
        "-initial_pres_fac",
        "8",
    ]
    output = "route_log.out"


all = [
    ammp,
    applu,
    apsi,
    art,
    art110,
    art470,
    equake,
    facerec,
    fma3d,
    galgel,
    lucas,
    mesa,
    mgrid,
    sixtrack,
    swim,
    wupwise,
    bzip2_source,
    bzip2_graphic,
    bzip2_program,
    crafty,
    eon_kajiya,
    eon_cook,
    eon_rushmeier,
    gap,
    gcc_166,
    gcc_200,
    gcc_expr,
    gcc_integrate,
    gcc_scilab,
    gzip_source,
    gzip_log,
    gzip_graphic,
    gzip_random,
    gzip_program,
    mcf,
    parser,
    perlbmk_diffmail,
    perlbmk_makerand,
    perlbmk_perfect,
    perlbmk_splitmail1,
    perlbmk_splitmail2,
    perlbmk_splitmail3,
    perlbmk_splitmail4,
    twolf,
    vortex1,
    vortex2,
    vortex3,
    vpr_place,
    vpr_route,
]

__all__ = [x.__name__ for x in all]

if __name__ == "__main__":
    from pprint import pprint

    for bench in all:
        for input_set in "ref", "test", "train":
            print(f"class: {bench.__name__}")
            x = bench("x86", "linux", input_set)
            print(f"{x}: {input_set}")
            pprint(x.makeProcessArgs())
            print()
